import type { SceneOptions, SceneManagerOptions } from './scene/sceneManager';
import { SceneManager } from './scene/sceneManager';

interface GameOptions {
  parent: string | HTMLElement;
  width?: number;
  height?: number;
  scenes?: SceneOptions[];
}

enum GAME_STATE {
  RUNNING = 1,
  PAUSED = 2,
  STOP = 3,
}

const MS_PER_FRAME = Math.floor(1000 / 60);

export class Game {
  public readonly width: number;

  public readonly height: number;

  private canvas: HTMLCanvasElement;

  private canvasCtx: CanvasRenderingContext2D;

  private state: GAME_STATE;

  private lastTime: number = 0;

  private lag: number = 0;

  private runningRAF: number;

  sceneManager: SceneManager;

  constructor (options: GameOptions) {
    const parent = this.queryParent(options.parent);
    const canvas = this.initCanvas(options.width, options.height);
    parent.appendChild(canvas);

    this.canvas = canvas;
    this.canvasCtx = canvas.getContext('2d');

    this.sceneManager = new SceneManager({ scenes: options.scenes }, this);

    this.width = options.width;
    this.height = options.height;

    this.start();
  }

  private queryParent (parent: string | HTMLElement): HTMLElement {
    if (parent instanceof HTMLElement) {
      return parent;
    }
    const el: HTMLElement = document.querySelector(parent[0] === '#' ? parent : `#${parent}`);
    return el ?? document.body;
  }

  private initCanvas (width: number, height: number) {
    const canvas = document.createElement('canvas');
    width && (canvas.width = width);
    height && (canvas.height = height);
    return canvas;
  }

  private update (elapsedTime: number) {
    this.sceneManager.update(elapsedTime);
  }

  private clearWorld () {
    this.canvasCtx.fillStyle = "black";
    this.canvasCtx.fillRect(0, 0, this.canvas.width, this.canvas.height);
  }

  private render () {
    this.clearWorld();
    this.sceneManager.render(this.canvasCtx);
  }

  private loopRAF (currentTime: number) {
    if (this.state !== GAME_STATE.RUNNING) {
      return;
    }

    const elapsedTime = currentTime - this.lastTime;
    this.lastTime = currentTime;
    this.lag += elapsedTime;

    while (this.lag >= MS_PER_FRAME) {
      this.update(elapsedTime);
      this.lag -= MS_PER_FRAME;
    }

    this.render();

    window.requestAnimationFrame(this.loopRAF.bind(this));
  }

  public start () {
    if (!this.state || this.state === GAME_STATE.PAUSED) {
      this.state = GAME_STATE.RUNNING;

      if (this.runningRAF) {
        window.cancelAnimationFrame(this.runningRAF);
      }

      this.lastTime = window.performance.now();
      this.loopRAF(this.lastTime);
    }
  }

  public pause () {
    if (this.state === GAME_STATE.RUNNING) {
      this.state = GAME_STATE.PAUSED;
    }
  }

  public stop () {
    this.state = GAME_STATE.STOP;
    // TODO: stop and destroy
  }
}
